package com.econ.powercloud.ems.service.biz;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.econ.powercloud.common.base.facade.enums.StatusType;
import com.econ.powercloud.common.base.facade.pojo.MsgResult;
import com.econ.powercloud.common.base.facade.pojo.PageResult;
import com.econ.powercloud.common.base.facade.util.JacksonUtil;
import com.econ.powercloud.ems.common.enums.*;
import com.econ.powercloud.ems.constant.SysConstant;
import com.econ.powercloud.ems.dal.entity.EmsExportDataRecord;
import com.econ.powercloud.ems.pojo.*;
import com.econ.powercloud.ems.pojo.requestVO.ExportDataRequestVO;
import com.econ.powercloud.ems.pojo.requestVO.HisDataExportRequestVO;
import com.econ.powercloud.ems.pojo.scene.HisDataPidsVO;
import com.econ.powercloud.ems.pojo.scene.HisDataRequestVO;
import com.econ.powercloud.ems.pojo.scene.OpenApiHisDataVO;
import com.econ.powercloud.ems.service.common.QueryHisDataCommonService;
import com.econ.powercloud.ems.service.dao.EmsExportDataRecordDAO;
import com.econ.powercloud.ems.util.DownLoadFileUtils;
import com.econ.powercloud.ems.util.ExportExcelUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author : zjf
 * @Description : 数据导出service
 * @Date : 2023/5/31 11:47
 * @Modified by : zjf
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ExportDataService {
    public static final String EXCEL_SUFFIX = ".xlsx";
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    @Value("${ems.export-data.path}")
    private String path;
    @Resource(name="asyncPoolTaskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    private final QueryHisDataCommonService queryHisDataCommonService;
    private final DeviceService deviceService;
    private final EmsExportDataRecordDAO emsExportDataRecordDAO;

    /**
     * 按照时间间隔导出设备点号历史数据
     */
    public void exportHisDitData(HisDataExportRequestVO<DitDataVO> requestVO){
        String fileName = requestVO.getFileName();
        requestVO.setFileName(requestVO.getFileName()+ "_" + DateFormatUtils.format(new Date(), "yyyyMMddHHmm"));
        EmsExportDataRecord record = EmsExportDataRecord.builder()
                .type(ExportDataType.DEVICE_DIT_HIS_DATA.getCode()).fileName(fileName)
                .timeIntervalType(requestVO.getTimeIntervalType())
                .filePath(path + "/" + requestVO.getFileName() + EXCEL_SUFFIX)
                .exportStartTime(requestVO.getStartTime()).exportEndTime(requestVO.getEndTime())
                .status(ExportDataStatusEnum.DEAL.getCode()).gmtCreate(System.currentTimeMillis())
                .build();
        emsExportDataRecordDAO.save(record);
        //异步执行下载操作
        threadPoolTaskExecutor.execute(()-> exportHisDitDataExcel(requestVO,record));
    }

    /**
     * 获取导出记录
     * @param requestVO
     * @param pageNo
     * @param pageSize
     * @return
     */
    public PageResult<List<ExportDataRecordVO>> getRecordList(ExportDataRequestVO requestVO, Integer pageNo, Integer pageSize){
        Page<EmsExportDataRecord> page = emsExportDataRecordDAO.selectListByCondition(requestVO.getType(), requestVO.getStartTime(),
                requestVO.getEndTime(),requestVO.getFileName(),requestVO.getStatus(),pageNo,pageSize);
        List<ExportDataRecordVO> result = Lists.newArrayList();
        if(CollectionUtils.isNotEmpty(page.getRecords())){
            result = JacksonUtil.convertToList(page.getRecords(),ExportDataRecordVO.class);
            result.stream().forEach(d->{
                d.setTypeName(ExportDataType.fromCode(d.getType()).getDesc());
                d.setStatusName(ExportDataStatusEnum.fromCode(d.getStatus()).getDesc());
                d.setTimeIntervalTypeName(TimeIntervalType.fromCode(d.getTimeIntervalType()).getDesc());
            });
        }
        return PageResult.<List<ExportDataRecordVO>>builder().data(result).pageNo((int)page.getCurrent()).totalCount((int)page.getTotal()).build();
    }

    /**
     * 下载文件
     * @param id
     * @param response
     */
    public void downLoadFile(Integer id, HttpServletResponse response){
        EmsExportDataRecord record = emsExportDataRecordDAO.getById(id);
        Assert.notNull(record,"该记录不存在！");
        DownLoadFileUtils.downLoad(record.getFilePath(),record.getFileName(),response);
    }

    /**
     * 删除导出记录
     * @param id
     */
    public void deleteRecord(Integer id){
        emsExportDataRecordDAO.removeById(id);
    }

    /**
     * 获取设备点号历史数据，并存入服务器中
     * @param requestVO
     * @param record
     */
    private void exportHisDitDataExcel(HisDataExportRequestVO<DitDataVO> requestVO,EmsExportDataRecord record){
        try {
            TimeIntervalType timeIntervalType = TimeIntervalType.fromCode(requestVO.getTimeIntervalType());
            //根据传入的list给点号设置顺序,key:pid
            Map<String,Integer> pidSortMap = Maps.newHashMap();
            for (int i=0; i<requestVO.getHisDataVOS().size(); i++){
                DitDataVO d = requestVO.getHisDataVOS().get(i);
                pidSortMap.put(d.getInitDeviceCode() + SysConstant.SPOT + d.getDit(),i);
            }
            List<DitDataExtraVO> extraVOList = deviceService.getDitExtraByDitDataList(requestVO.getHisDataVOS());
            //Pid对应的基本信息map
            Map<String,DitDataExtraVO> ditDataMap = extraVOList.stream().collect(Collectors.toMap(d->d.getInitDeviceCode() + SysConstant.SPOT + d.getDit(),p->p,(p1,p2)->p1));
            List<HisDataPidsVO> ycHisDataPidsVOList = Lists.newArrayList();
            List<HisDataPidsVO> yxHisDataPidsVOList = Lists.newArrayList();
            requestVO.getHisDataVOS().stream().forEach(item -> {
                if (item.getDitType().equals(DitTypeEnum.YC.getCode()) || item.getDitType().equals(DitTypeEnum.CAL_YC.getCode())) {
                    ycHisDataPidsVOList.add(HisDataPidsVO.builder().deviceCode(item.getInitDeviceCode()).dits(Lists.newArrayList(item.getDit())).build());
                } else if (item.getDitType().equals(DitTypeEnum.YX.getCode()) || item.getDitType().equals(DitTypeEnum.CAL_YX.getCode())) {
                    yxHisDataPidsVOList.add(HisDataPidsVO.builder().deviceCode(item.getInitDeviceCode()).dits(Lists.newArrayList(item.getDit())).build());
                }
            });
            //查询点号历史数据
            List<OpenApiHisDataVO> openApiHisDataVOList = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(ycHisDataPidsVOList)) {
                HisDataRequestVO ycHisDataRequestVO = HisDataRequestVO.builder().pids(ycHisDataPidsVOList).startTime(requestVO.getStartTime()).endTime(requestVO.getEndTime()).build();
                Map<String, List<OpenApiHisDataVO>> ycDataByListPid = queryHisDataCommonService.getYcDataByListPid(ycHisDataRequestVO);
                ycDataByListPid.values().stream().forEach(item -> openApiHisDataVOList.addAll(item));
            }
            if (CollectionUtils.isNotEmpty(yxHisDataPidsVOList)) {
                HisDataRequestVO yxHisDataRequestVO = HisDataRequestVO.builder().pids(yxHisDataPidsVOList).startTime(requestVO.getStartTime()).endTime(requestVO.getEndTime()).build();
                Map<String, List<OpenApiHisDataVO>> yxDataByListPid = queryHisDataCommonService.getYxDataByListPid(yxHisDataRequestVO);
                yxDataByListPid.values().stream().forEach(item -> openApiHisDataVOList.addAll(item));
            }
            //按照时间颗粒度清洗数据
            List<OpenApiHisDataVO> hisDataVOList = Lists.newArrayList();
            Map<String,List<OpenApiHisDataVO>> hisDataByPidMap = openApiHisDataVOList.stream().collect(Collectors.groupingBy(d->d.getPid()));
            requestVO.getHisDataVOS().stream().forEach(d->{
                String pid = d.getInitDeviceCode() + SysConstant.SPOT + d.getDit();
                List<OpenApiHisDataVO> dataVOS = hisDataByPidMap.get(pid);
                hisDataVOList.addAll(initHisDataByTimeGranularity(requestVO.getStartTime(), requestVO.getEndTime(),timeIntervalType.getMilliseconds(),pid,
                        CollectionUtils.isEmpty(dataVOS)?Collections.emptyList():dataVOS.stream().sorted(Comparator.comparing(OpenApiHisDataVO::getTime)).collect(Collectors.toList())));
            });
            //给点号排序
            hisDataVOList.stream().forEach(d-> d.setSortNum(pidSortMap.get(d.getPid())));
            //按照时间分组,每一组按照排序升序排序
            Map<Long,List<OpenApiHisDataVO>> hisDataByTimeMap = hisDataVOList.stream().collect(Collectors.groupingBy(OpenApiHisDataVO::getTime,
                    Collectors.collectingAndThen(Collectors.toList(), list -> list.stream().sorted(Comparator.comparingInt(OpenApiHisDataVO::getSortNum))
                            .collect(Collectors.toList()))));
            //报表中的数据
            List<HisDitDataExportVO> bodyExportList = hisDataByTimeMap.values().stream().map(d-> HisDitDataExportVO.builder()
                    .time(d.get(0).getTime()).formatTime(timeIntervalType.getSdf().format(new Date(d.get(0).getTime()))).list(d).build())
                    .sorted(Comparator.comparing(HisDitDataExportVO::getTime)).collect(Collectors.toList());
            //报表中的表头数据
            List<DitDataExtraVO> headers = requestVO.getHisDataVOS().stream().map(d-> ditDataMap.get(d.getInitDeviceCode() + SysConstant.SPOT + d.getDit())).collect(Collectors.toList());
            Map<String, Object> params = new HashMap<>();
            params.put("items", bodyExportList);
            params.put("headers",headers);
            ExcelTemplateEnum templateEnum = ExcelTemplateEnum.HIS_DATA_EXPORT;
            MsgResult result = ExportExcelUtils.export(params, templateEnum.getTemplatePath(), requestVO.getFileName(),path);
            if(StatusType.OK.getCode() == result.getStatus()){
                record.setStatus(ExportDataStatusEnum.SUCCESS.getCode());
            }else {
                record.setStatus(ExportDataStatusEnum.FAIL.getCode());
            }
            record.setGmtModify(System.currentTimeMillis());
            emsExportDataRecordDAO.updateById(record);
        }catch (Exception e){
            record.setStatus(ExportDataStatusEnum.FAIL.getCode());
            record.setGmtModify(System.currentTimeMillis());
            emsExportDataRecordDAO.updateById(record);
            log.error(String.format("导出数据出错：请求参数：%s,错误原因:%s",JacksonUtil.toJSONString(requestVO),e.getMessage()));
        }
    }

    /**
     * 按照时间颗粒度清洗历史数据
     * @param startTime : 开始时间(时间颗粒度对应的第一个时间戳)
     * @param endTime: 结束时间
     * @param increaseTime : 时间颗粒度对应的毫秒级数据
     * @param hisDataList :  历史数据(只能是一个点号的历史数据,按照时间正序排序)
     * @return
     */
    private static List<OpenApiHisDataVO> initHisDataByTimeGranularity(Long startTime,Long endTime,Long increaseTime,String pid,List<OpenApiHisDataVO> hisDataList){
        List<OpenApiHisDataVO> result = Lists.newArrayList();
        //记录当前需要获取的时间戳,每满足一次条件后自动增加
        Long time = startTime;
        if(CollectionUtils.isEmpty(hisDataList)){
            while (time <= endTime){
                result.add(OpenApiHisDataVO.builder().pid(pid)
                        .time(time).v("-").build());
                time = time + increaseTime;
            }
            return result;
        }
        for (int i=0; i<hisDataList.size(); i++){
            if(Objects.equals(time,hisDataList.get(i).getTime())){
                result.add(OpenApiHisDataVO.builder().pid(pid)
                        .time(time).v(hisDataList.get(i).getV()).build());
                time = time + increaseTime;
                continue;
            }else if(time < hisDataList.get(i).getTime()){
                //此循环是避免点号在多个时间间隔内无数据
                while (time < hisDataList.get(i).getTime()){
                    //说明当前需要获取的时间戳对应的数据是循环中的上一个值,如果没数据,值设置成"-"
                    result.add(OpenApiHisDataVO.builder()
                            .pid(pid).time(time).v(i>0?hisDataList.get(i-1).getV():"-")
                            .build());
                    time = time + increaseTime;
                }
            }
        }
        while (time <= endTime){
            result.add(OpenApiHisDataVO.builder().pid(hisDataList.get(hisDataList.size()-1).getPid())
                    .time(time).v(hisDataList.get(hisDataList.size()-1).getV()).build());
            time = time + increaseTime;
        }
        return result;
    }

}
